import type {
  VodAssetInfo,
  PlayInfo,
  ChunkedUploadParams,
  UploadProgress,
  CreateAssetResponse,
  UploadAuthResponse,
  ChunkUploadInfo,
  UploadedPart,
  DirectUploadParams,
  PlayUrl
} from '../types/vod'
import CryptoJS from 'crypto-js'
// 移除jQuery依赖，现在使用fetch API

/**
 * 华为云VOD服务工具类
 */
export class HuaweiVodService {
  private readonly baseUrl: string
  private readonly projectId: string

  // Token认证相关
  private readonly iamUsername: string
  private readonly iamPassword: string
  private readonly domainName: string
  private readonly projectName: string
  private readonly tokenCacheKey = 'huawei_vod_token'
  private readonly tokenExpireKey = 'huawei_vod_token_expire'

  constructor(config?: {
    projectId?: string
    region?: string
    endpoint?: string
  }) {
    this.projectId = config?.projectId || '4fc64d1318fd474faf47bda459a5ceda'
    // 使用代理路径避免跨域问题
    this.baseUrl = '/vod-api'

    // Token认证配置
    this.iamUsername = import.meta.env.VITE_VOD_IAMUSERNAME
    this.iamPassword = import.meta.env.VITE_VOD_IAMPASSWORD
    this.domainName = import.meta.env.VITE_VOD_DOMAINNAME
    this.projectName = import.meta.env.VITE_VOD_PROJECTNAME
  }


  /**
   * 获取IAM Token
   */
  private async getIamToken(): Promise<string> {
    // 检查localStorage中缓存的Token是否有效（提前5分钟过期）
    const now = Date.now()
    const cachedToken = localStorage.getItem(this.tokenCacheKey)
    const tokenExpireTime = parseInt(localStorage.getItem(this.tokenExpireKey) || '0')
    
    if (cachedToken && tokenExpireTime > now + 5 * 60 * 1000) {
      return cachedToken
    }

    // 使用代理路径避免跨域问题
    const proxyUrl = '/iam-api/v3/auth/tokens'

    const requestBody = {
      auth: {
        identity: {
          methods: ['password'],
          password: {
            user: {
              name: this.iamUsername,
              password: this.iamPassword,
              domain: {
                name: this.domainName
              }
            }
          }
        },
        scope: {
          project: {
            name: this.projectName
          }
        }
      }
    }

    try {
      const headers = new Headers()
      headers.append('Content-Type', 'application/json')

      const requestOptions: RequestInit = {
        method: 'POST',
        headers: headers,
        body: JSON.stringify(requestBody),
        redirect: 'follow'
      }

      const response = await fetch(proxyUrl, requestOptions)

      if (!response.ok) {
        const errorText = await response.text()
        let errorMsg = errorText
        try {
          const errorJson = JSON.parse(errorText)
          errorMsg = errorJson.error?.message || errorText
        } catch {
          // 如果不是JSON格式，使用原始错误文本
        }
        throw new Error(`获取Token失败: ${response.status} ${response.statusText} - ${errorMsg}`)
      }

      const token = response.headers.get('X-Subject-Token')
      if (!token) {
        throw new Error('未获取到Token')
      }

      await response.json() // 读取响应体但不使用

      // 缓存Token到localStorage（24小时有效期，提前5分钟过期）
      const expireTime = now + 24 * 60 * 60 * 1000 - 5 * 60 * 1000
      localStorage.setItem(this.tokenCacheKey, token)
      localStorage.setItem(this.tokenExpireKey, expireTime.toString())

      return token
    } catch (error) {
      console.error('获取IAM Token失败:', error)
      throw error
    }
  }

  /**
   * 清理过期的Token缓存
   */
  private clearExpiredToken(): void {
    const now = Date.now()
    const tokenExpireTime = parseInt(localStorage.getItem(this.tokenExpireKey) || '0')
    
    if (tokenExpireTime <= now) {
      localStorage.removeItem(this.tokenCacheKey)
      localStorage.removeItem(this.tokenExpireKey)
    }
  }

  /**
   * 发送HTTP请求 - 使用Token认证（用于分段上传）
   */
  private async requestWithToken<T>(
    method: string,
    endpoint: string,
    data?: any
  ): Promise<T> {
    try {
      // 获取Token
      const token = await this.getIamToken()

      // 使用代理路径发送请求
      const proxyUrl = `${this.baseUrl}${endpoint}`

      // 构建fetch请求头
      const headers = new Headers()
      headers.append('X-Auth-Token', token)

      // 构建请求选项
      const requestOptions: RequestInit = {
        method: method,
        headers: headers,
        redirect: 'follow'
      }

      // 只有POST/PUT/PATCH请求才添加body
      if (['POST', 'PUT', 'PATCH'].includes(method) && data) {
        headers.append('Content-Type', 'application/json')
        requestOptions.body = JSON.stringify(data)
      }

      const response = await fetch(proxyUrl, requestOptions)

      if (!response.ok) {
        const errorText = await response.text()
        throw new Error(`API请求失败: ${response.status} ${response.statusText} - ${errorText}`)
      }

      // 尝试解析JSON响应
      const contentType = response.headers.get('content-type')
      if (contentType && contentType.includes('application/json')) {
        return await response.json()
      } else {
        // 如果不是JSON响应，返回文本
        return await response.text() as any
      }
    } catch (error) {
      console.error('VOD API请求错误:', error)
      throw error
    }
  }



  /**
   * 获取媒资信息
   * @param assetId 媒资ID
   * @returns 媒资信息
   */
  async getAssetInfo(assetId: string): Promise<VodAssetInfo> {
    const response = await this.requestWithToken<{
      asset_id: string
      status: string
      base_info?: {
        title: string
        create_time: string
        last_modified: string
        meta_data?: {
          video_size?: number
          duration?: number
        }
        cover_info_array?: Array<{ cover_url: string }>
      }
    }>('GET', `/v1.0/${this.projectId}/asset/details?asset_id=${assetId}&categories=base_info`)

    if ((response as any).asset_id) {
      const baseInfo = (response as any).base_info
      return {
        assetId: (response as any).asset_id,
        title: baseInfo?.title || '',
        status: (response as any).status as VodAssetInfo['status'],
        createTime: baseInfo?.create_time || '',
        updateTime: baseInfo?.last_modified || '',
        size: baseInfo?.meta_data?.video_size,
        duration: baseInfo?.meta_data?.duration,
        coverUrl: baseInfo?.cover_info_array?.[0]?.cover_url
      }
    }

    throw new Error('获取媒资信息失败')
  }

  /**
   * 获取播放地址（使用专门的播放接口）
   * @param assetId 媒资ID
   * @returns 播放信息
   */
  async getPlayInfo(assetId: string): Promise<PlayInfo> {
    const response = await this.requestWithToken<{
      asset_id: string
      base_info?: {
        title: string
        asset_status: string
        meta_data?: {
          duration?: number
          duration_ms?: number
          width?: number
          height?: number
          video_size?: number
          bit_rate?: number
        }
        sign_url?: string
        cover_info_array?: Array<{ cover_url?: string }>
      }
      transcode_info?: {
        output?: Array<{
          play_type: string
          meta_data?: {
            duration?: number
            duration_ms?: number
            width?: number
            height?: number
            video_size?: number
            bit_rate?: number
            quality?: string
          }
        }>
        transcode_status?: string
      }
    }>('GET', `/v1.0/${this.projectId}/asset/details?asset_id=${assetId}&categories=base_info,transcode_info`)

    if ((response as any).asset_id) {
      const playUrls: PlayUrl[] = []

      // 使用sign_url作为播放地址
      if ((response as any).base_info?.sign_url) {
        playUrls.push({
          url: (response as any).base_info.sign_url,
          quality: 'ORIGINAL',
          format: 'MP4',
          bitrate: (response as any).base_info.meta_data?.bit_rate,
          resolution: (response as any).base_info.meta_data?.width && (response as any).base_info.meta_data?.height ?
            `${(response as any).base_info.meta_data.width}x${(response as any).base_info.meta_data.height}` : undefined,
          size: (response as any).base_info.meta_data?.video_size
        })
      }

      // 如果没有播放地址，抛出错误
      if (playUrls.length === 0) {
        throw new Error('未找到可用的播放地址，媒资可能还在处理中')
      }

      return {
        assetId: (response as any).asset_id,
        playUrls,
        coverUrl: (response as any).base_info?.cover_info_array?.[0]?.cover_url,
        duration: (response as any).base_info?.meta_data?.duration ||
          Math.floor(((response as any).base_info?.meta_data?.duration_ms || 0) / 1000)
      }
    }

    throw new Error('获取播放信息失败')
  }


  /**
   * 等待转码完成
   * @param assetId 媒资ID
   * @param maxWaitTime 最大等待时间
   * @param pollInterval 轮询间隔
   */
  private async waitForTranscodeComplete(
    assetId: string,
    maxWaitTime: number = 600000, // 10分钟
    pollInterval: number = 10000 // 10秒
  ): Promise<void> {
    const startTime = Date.now()

    while (Date.now() - startTime < maxWaitTime) {
      const assetInfo = await this.getAssetInfo(assetId)

      if (assetInfo.status === 'TRANSCODE_SUCCEED' || assetInfo.status === 'PUBLISHED') {
        return
      }

      if (assetInfo.status === 'TRANSCODE_FAILED') {
        throw new Error('转码失败')
      }

      await new Promise(resolve => setTimeout(resolve, pollInterval))
    }

    throw new Error('转码等待超时')
  }

  /**
   * 计算文件MD5值（使用导入的CryptoJS）
   */
  private async calculateMD5(blob: Blob): Promise<string> {
    return new Promise((resolve, reject) => {
      const reader = new FileReader()
      reader.onload = (event) => {
        try {
          const arrayBuffer = event.target?.result as ArrayBuffer
          // 使用导入的CryptoJS计算MD5
          const wordArray = CryptoJS.lib.WordArray.create(arrayBuffer)
          const hash = CryptoJS.MD5(wordArray)
          const base64Hash = CryptoJS.enc.Base64.stringify(hash)
          resolve(base64Hash)
        } catch (error) {
          reject(new Error(`MD5计算失败: ${error instanceof Error ? error.message : '未知错误'}`))
        }
      }
      reader.onerror = () => reject(new Error('文件读取失败'))
      reader.readAsArrayBuffer(blob)
    })
  }

  /**
   * 根据文件扩展名获取Content-Type
   */
  private getContentType(fileName: string): string {
    const ext = fileName.split('.').pop()?.toLowerCase() || ''
    const contentTypes: Record<string, string> = {
      'mp4': 'video/mp4',
      'mov': 'video/quicktime',
      'mxf': 'application/mxf',
      'ts': 'video/mp2t',
      'mpg': 'video/mpeg',
      'flv': 'video/flv',
      'wmv': 'video/x-ms-wmv',
      'avi': 'video/x-msvideo',
      'm4v': 'video/m4v',
      'f4v': 'application/f4v',
      'mpeg': 'video/mpeg',
      'm3u8': 'application/octet-stream',
      '3gp': 'video/3gpp',
      'asf': 'video/x-ms-asf',
      'mkv': 'video/x-matroska',
      'webm': 'video/webm',
      'mpd': 'video/dash',
      'mp3': 'audio/mp3',
      'wma': 'audio/wma',
      'ape': 'audio/ape',
      'flac': 'audio/flac',
      'aac': 'audio/aac',
      'ac3': 'audio/ac3',
      'mmf': 'audio/mmf',
      'amr': 'audio/amr',
      'm4a': 'audio/m4a',
      'm4r': 'audio/m4r',
      'ogg': 'audio/ogg',
      'wav': 'audio/wav',
      'wv': 'audio/wv',
      'mp2': 'audio/mp2'
    }
    return contentTypes[ext || ''] || 'application/octet-stream'
  }

  /**
   * 创建媒资 - 使用Token认证
   */
  private async createAsset(params: ChunkedUploadParams): Promise<CreateAssetResponse> {
    const videoType = params.file.name.split('.').pop()?.toUpperCase() || 'MP4'

    const requestData = {
      title: params.title,
      description: params.description || '',
      category_id: params.categoryId,
      tags: params.tags?.join(',') || '',
      video_name: params.file.name,
      video_type: videoType,
      auto_publish: params.autoPublish ? 1 : 0,
      template_group_name: params.transcodeTemplateGroupName || 'system_template_group'
    }

    const response = await this.requestWithToken<CreateAssetResponse>(
      'POST',
      `/v1.0/${this.projectId}/asset`,
      requestData
    )

    if ((response as any).asset_id) {
      return response as CreateAssetResponse
    }

    throw new Error('创建媒资失败')
  }

  /**
   * 获取分段上传授权 - 使用Token认证 (v1.1接口)
   */
  private async getUploadAuth(params: {
    httpVerb: string
    contentType?: string
    bucket: string
    objectKey: string
    contentMd5?: string
    uploadId?: string
    partNumber?: number
  }): Promise<UploadAuthResponse> {
    const queryParams = new URLSearchParams({
      http_verb: params.httpVerb,
      bucket: params.bucket,
      object_key: params.objectKey
    })

    if (params.contentType) {
      queryParams.append('content_type', params.contentType)
    }
    if (params.contentMd5) {
      queryParams.append('content_md5', params.contentMd5)
    }
    if (params.uploadId) {
      queryParams.append('upload_id', params.uploadId)
    }
    if (params.partNumber) {
      queryParams.append('part_number', params.partNumber.toString())
    }

    const response = await this.requestWithToken<UploadAuthResponse>(
      'GET',
      `/v1.1/${this.projectId}/asset/authority?${queryParams.toString()}`
    )

    if ((response as any).sign_str) {
      return response as UploadAuthResponse
    }

    throw new Error('获取上传授权失败')
  }


  /**
   * 初始化分段上传任务 - 使用jQuery ajax (v1.1接口)
   */
  private async initiateMultipartUpload(
    _bucket: string,
    _objectKey: string,
    signStr: string,
    contentType: string
  ): Promise<string> {
    // v1.1接口返回的sign_str已经是完整的签名URL
    const signedUrl = signStr

    const headers = new Headers()
    headers.append('Content-Type', contentType)

    const requestOptions: RequestInit = {
      method: 'POST',
      headers: headers,
      redirect: 'follow'
    }

    const response = await fetch(signedUrl, requestOptions)

    if (!response.ok) {
      const errorText = await response.text()
      throw new Error(`初始化分段上传失败: ${response.status} ${response.statusText} - ${errorText}`)
    }

    const xmlText = await response.text()

    const parser = new DOMParser()
    const xmlDoc = parser.parseFromString(xmlText, 'text/xml')
    const uploadId = xmlDoc.getElementsByTagName('UploadId')[0]?.textContent

    if (!uploadId) {
      throw new Error('未获取到UploadId')
    }

    return uploadId
  }

  /**
   * 上传单个分段 - 使用jQuery ajax (v1.1接口)
   */
  private async uploadChunk(
    _bucket: string,
    _objectKey: string,
    signStr: string,
    _uploadId: string,
    _partNumber: number,
    chunk: Blob,
    md5: string
  ): Promise<string> {
    // v1.1接口返回的sign_str已经是完整的签名URL
    const signedUrl = signStr

    const headers = new Headers()
    headers.append('Content-MD5', md5)
    headers.append('Content-Type', 'application/octet-stream')

    const requestOptions: RequestInit = {
      method: 'PUT',
      headers: headers,
      body: chunk,
      redirect: 'follow'
    }

    const response = await fetch(signedUrl, requestOptions)

    if (!response.ok) {
      const errorText = await response.text()
      throw new Error(`分段上传失败: ${response.status} ${response.statusText} - ${errorText}`)
    }

    const etag = response.headers.get('ETag')
    if (!etag) {
      throw new Error('未获取到ETag')
    }

    return etag.replace(/"/g, '') // 移除引号
  }

  /**
   * 列举已上传的分段 - 使用jQuery ajax (v1.1接口)
   */
  private async listUploadedParts(signedUrl: string): Promise<UploadedPart[]> {
    const response = await fetch(signedUrl, {
      method: 'GET',
      redirect: 'follow'
    })

    if (!response.ok) {
      const errorText = await response.text()
      throw new Error(`列举分段失败: ${response.status} ${response.statusText} - ${errorText}`)
    }

    const xmlText = await response.text()

    const parser = new DOMParser()
    const xmlDoc = parser.parseFromString(xmlText, 'text/xml')

    const parts: UploadedPart[] = []
    const partElements = xmlDoc.getElementsByTagName('Part')

    for (let i = 0; i < partElements.length; i++) {
      const part = partElements[i]
      const partNumberElement = part?.getElementsByTagName('PartNumber')[0]
      const etagElement = part?.getElementsByTagName('ETag')[0]
      const sizeElement = part?.getElementsByTagName('Size')[0]
      const lastModifiedElement = part?.getElementsByTagName('LastModified')[0]

      const partNumber = parseInt(partNumberElement?.textContent || '0')
      const etag = etagElement?.textContent?.replace(/"/g, '') || ''
      const size = parseInt(sizeElement?.textContent || '0')
      const lastModified = lastModifiedElement?.textContent || ''

      parts.push({
        PartNumber: partNumber,
        ETag: etag,
        Size: size,
        LastModified: lastModified
      })
    }

    return parts
  }

  /**
   * 合并分段 - 使用jQuery ajax (v1.1接口)
   */
  private async completeMultipartUpload(
    _bucket: string,
    _objectKey: string,
    signStr: string,
    _uploadId: string,
    parts: UploadedPart[]
  ): Promise<void> {
    // v1.1接口返回的sign_str已经是完整的签名URL
    const signedUrl = signStr

    // 构建合并请求的XML
    const sortedParts = parts.sort((a, b) => a.PartNumber - b.PartNumber)
    const partsXml = sortedParts.map(part =>
      `<Part><PartNumber>${part.PartNumber}</PartNumber><ETag>"${part.ETag}"</ETag></Part>`
    ).join('')

    const xmlBody = `<CompleteMultipartUpload>${partsXml}</CompleteMultipartUpload>`

    const headers = new Headers()
    headers.append('Content-Type', 'application/xml')

    const requestOptions: RequestInit = {
      method: 'POST',
      headers: headers,
      body: xmlBody,
      redirect: 'follow'
    }

    const response = await fetch(signedUrl, requestOptions)

    if (!response.ok) {
      const errorText = await response.text()
      throw new Error(`合并分段失败: ${response.status} ${response.statusText} - ${errorText}`)
    }
  }

  /**
   * 确认媒资上传 - 使用Token认证
   */
  private async confirmAssetUpload(assetId: string): Promise<void> {
    const response = await this.requestWithToken(
      'POST',
      `/v1.0/${this.projectId}/asset/status/uploaded`,
      {
        asset_id: assetId,
        status: 'CREATED'
      }
    )

    if ((response as any).error_code) {
      throw new Error(`确认媒资上传失败: ${(response as any).error_msg}`)
    }
  }

  /**
   * 将文件分割成块
   */
  private createChunks(file: File, chunkSize: number = 10 * 1024 * 1024): ChunkUploadInfo[] {
    const chunks: ChunkUploadInfo[] = []
    const totalChunks = Math.ceil(file.size / chunkSize)

    for (let i = 0; i < totalChunks; i++) {
      const start = i * chunkSize
      const end = Math.min(start + chunkSize, file.size)
      const chunk = file.slice(start, end)

      chunks.push({
        partNumber: i + 1, // OBS分段号从1开始
        chunk,
        md5: '', // 将在上传时计算
        size: chunk.size
      })
    }

    return chunks
  }

  /**
   * 媒资直接上传（20MB以下的文件）
   * @param file 要上传的文件
   * @param params 上传参数
   * @returns 上传结果
   */
  async uploadAssetDirect(params: DirectUploadParams): Promise<{
    assetId: string
    status: string
    playInfo?: any
  }> {
    const { file } = params
    try {
      // 步骤1: 创建媒资
      const createAssetResponse = await this.createAssetForUpload(file, params)

      // 步骤2: 直接上传文件到OBS
      await this.uploadFileToObs(file, createAssetResponse.video_upload_url)

      // 步骤3: 确认媒资上传
      await this.confirmAssetUpload(createAssetResponse.asset_id)

      return {
        assetId: createAssetResponse.asset_id,
        status: 'UPLOADED'
      }
    } catch (error) {
      console.error('直接上传失败:', error)
      throw error
    }
  }

  /**
   * 创建媒资：上传方式
   */
  private async createAssetForUpload(
    file: File,
    params: any
  ): Promise<{
    asset_id: string
    video_upload_url: string
    cover_upload_url?: string
    subtitle_upload_urls?: string[]
    target: {
      bucket: string
      location: string
      object: string
    }
  }> {
    const requestData = {
      title: params.title,
      description: params.description || '',
      category_id: params.categoryId,
      video_name: file.name,
      video_type: this.getContentType(file.name).split('/')[1]?.toUpperCase() || 'MP4',
      cover_type: params.coverType || 'PNG',
      tags: params.tags?.join(',') || '',
      auto_publish: params.autoPublish ? 1 : 0,
      template_group_name: params.transcodeTemplateGroupName || 'system_template_group',
      subtitles: params.subtitles || [],
      thumbnail: params.thumbnail,
      review: params.review
    }

    const response = await this.requestWithToken<{
      asset_id: string
      video_upload_url: string
      cover_upload_url?: string
      subtitle_upload_urls?: string[]
      target: {
        bucket: string
        location: string
        object: string
      }
    }>('POST', `/v1.0/${this.projectId}/asset`, requestData)

    return response as any
  }

  /**
   * 直接上传文件到OBS（使用预签名URL）
   */
  private async uploadFileToObs(file: File, uploadUrl: string): Promise<void> {
    const contentType = this.getContentType(file.name)

    const headers = new Headers()
    headers.append('Content-Type', contentType)

    const requestOptions: RequestInit = {
      method: 'PUT',
      headers: headers,
      body: file,
      redirect: 'follow'
    }

    const response = await fetch(uploadUrl, requestOptions)

    if (!response.ok) {
      const errorText = await response.text()
      throw new Error(`文件上传失败: ${response.status} ${response.statusText} - ${errorText}`)
    }
  }

  /**
   * 媒资分段上传（大于20MB的文件）
   * @param params 上传参数
   * @returns 媒资ID
   */
  async uploadAssetByChunks(params: ChunkedUploadParams): Promise<string> {
    const { file, onProgress } = params
    const chunkSize = params.chunkSize || 10 * 1024 * 1024 // 默认10MB

    // 创建分段信息
    const chunks = this.createChunks(file, chunkSize)
    let uploadedBytes = 0

    const updateProgress = (stage: UploadProgress['stage'], currentChunk: number = 0) => {
      onProgress?.({
        uploadedBytes,
        totalBytes: file.size,
        percentage: Math.round((uploadedBytes / file.size) * 100),
        currentChunk,
        totalChunks: chunks.length,
        stage
      })
    }

    try {
      // 1. 创建媒资
      updateProgress('creating_asset')
      const assetInfo = await this.createAsset(params)
      const { asset_id, target } = assetInfo

      // 2. 获取初始化上传任务授权
      updateProgress('initializing')
      const initAuth = await this.getUploadAuth({
        httpVerb: 'POST',
        contentType: this.getContentType(file.name),
        bucket: target.bucket,
        objectKey: target.object
      })

      // 3. 初始化上传任务
      const uploadId = await this.initiateMultipartUpload(
        target.bucket,
        target.object,
        initAuth.sign_str,
        this.getContentType(file.name)
      )

      // 4. 上传所有分段
      updateProgress('uploading', 1)
      const uploadedParts: UploadedPart[] = []

      for (let i = 0; i < chunks.length; i++) {
        const chunkInfo = chunks[i]
        if (!chunkInfo) {
          continue
        }
        // 计算分段MD5
        chunkInfo.md5 = await this.calculateMD5(chunkInfo.chunk)

        // 获取分段上传授权
        const chunkAuth = await this.getUploadAuth({
          httpVerb: 'PUT',
          contentType: this.getContentType(file.name),
          bucket: target.bucket,
          objectKey: target.object,
          contentMd5: chunkInfo?.md5,
          uploadId,
          partNumber: chunkInfo?.partNumber
        })

        // 上传分段
        const etag = await this.uploadChunk(
          target.bucket,
          target.object,
          chunkAuth.sign_str,
          uploadId,
          chunkInfo.partNumber,
          chunkInfo.chunk,
          chunkInfo.md5
        )

        uploadedParts.push({
          PartNumber: chunkInfo.partNumber,
          ETag: etag,
          Size: chunkInfo.size,
          LastModified: new Date().toISOString()
        })

        uploadedBytes += chunkInfo.size
        updateProgress('uploading', i + 1)
      }

      // 5. 获取列举已上传分段授权
      updateProgress('merging')
      const listAuth = await this.getUploadAuth({
        httpVerb: 'GET',
        bucket: target.bucket,
        objectKey: target.object,
        uploadId
      })

      // 6. 列举已上传分段（验证）
      const listedParts = await this.listUploadedParts(listAuth.sign_str)

      if (listedParts.length !== chunks.length) {
        throw new Error(`分段数量不匹配: 期望${chunks.length}个，实际${listedParts.length}个`)
      }

      // 7. 获取合并分段授权
      const mergeAuth = await this.getUploadAuth({
        httpVerb: 'POST',
        bucket: target.bucket,
        objectKey: target.object,
        uploadId
      })

      // 8. 合并分段
      await this.completeMultipartUpload(
        target.bucket,
        target.object,
        mergeAuth.sign_str,
        uploadId,
        uploadedParts
      )

      // 9. 确认媒资上传
      updateProgress('confirming')
      await this.confirmAssetUpload(asset_id)

      updateProgress('completed')
      return asset_id

    } catch (error) {
      console.error('媒资分段上传失败:', error)
      throw error
    }
  }

  /**
   * 一键分段上传并获取播放地址（使用Token认证）
   * @param params 上传参数
   * @returns 播放信息
   */
  async uploadAssetAndGetPlayInfo(params: ChunkedUploadParams): Promise<PlayInfo> {
    // 1. 分段上传媒资
    const assetId = await this.uploadAssetByChunks(params)

    // 2. 等待转码完成（如果启用了自动转码）
    if (params.autoPublish !== false) {
      await this.waitForTranscodeComplete(assetId)
    }

    // 3. 获取播放信息
    return await this.getPlayInfo(assetId)
  }
}

/**
 * 创建VOD服务实例
 */
export function createVodService(config?: {
  projectId?: string
  region?: string
  endpoint?: string
}): HuaweiVodService {
  return new HuaweiVodService(config)
}
